import json
import time
import traceback
from typing import Callable

from fastapi import Request, Response
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from fastapi.routing import APIRoute
from loguru import logger

from exceptions import ServerException

__all__ = ['CenterRoute']


def timed_handler(next_handler) -> Callable:
    async def handler(request: Request) -> Response:
        before = time.monotonic()
        response = await next_handler(request)
        duration = time.monotonic() - before
        response.headers["X-Response-Time"] = str(duration)
        logger.info(f"{request.method} {request['path']} cost {duration:.2f}s")
        return response
    return handler


def warp_response_handler(next_handler) -> Callable:
    async def handler(request: Request) -> Response:
        response = await next_handler(request)
        if isinstance(response, JSONResponse):
            return JSONResponse(content={
                "code": 0,
                "message": "success",
                "data": json.loads(response.body)
            })
        return response
    return handler


def error_handler(next_handler) -> Callable:
    """
    需要在 warp_response_handler 之后调用
    :param next_handler:
    :return:
    """
    async def handler(request: Request) -> Response:
        try:
            response = await next_handler(request)
        except RequestValidationError as e:
            return JSONResponse(status_code=422, content={
                "code": -1,
                "message": "failed",
                "error": e.errors()
            })
        except ServerException as e:
            content = {
                "code": e.code,
                "message": "failed",
                "error": [str(e)]
            }
            if hasattr(e, "details"):
                content['details'] = [d.dict(exclude_unset=True) for d in e.details]
            return JSONResponse(status_code=200, content=content)
        except Exception as e:
            error_info = {"exceptions": str(e), "details": traceback.format_exc().split("\n")}
            body = await request.body()
            url_path = request.url.path
            logger.error(f"request uri: {url_path}, get error: {json.dumps(error_info, ensure_ascii=False, indent=2)}, "
                         f"body: {body}")
            return JSONResponse(status_code=500, content={
                "code": 5000,
                "message": "failed",
                "error": error_info
            })
        else:
            return response
    return handler


# 调用顺序，从上到下
handler_chain = [
    warp_response_handler,
    error_handler,
    timed_handler,
]


class CenterRoute(APIRoute):
    """
    自主实现route的原因：
    - https://github.com/tiangolo/fastapi/issues/954
    """
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            func = original_route_handler
            for handler in handler_chain:
                func = handler(func)
            response = await func(request)
            return response
        return custom_route_handler
